/*
 * CRenderer.cpp
 *
 *  Created on: 04.09.2011
 *      Author: kirill
 *      Copyright: LGPL
 */

#include "CRenderer.h"
#include <SDL/SDL.h>
#include "Constants.h"

static const USHORT PIXEL_COLOR_DEPTH = 32;

CRenderer::CRenderer() :
			frameBuffer(NULL),
			renderBuffer(NULL),
			textureBuffer(NULL),
			programmArray(NULL),
			initialized(false)
{

}

CRenderer::~CRenderer()
{
	for ( int i = 0; i < SHADER_TYPE_MAX; i++ )
	{
		if ( programmArray[i] != NULL )
		{
			delete programmArray[i];
		}
	}

	if ( textureBuffer != NULL )
	{
		delete textureBuffer;
	}

	if ( frameBuffer != NULL )
	{
		delete frameBuffer;
	}

	if ( renderBuffer != NULL )
	{
		delete renderBuffer;
	}
}

bool
CRenderer::Init( const SCREEN_DIMENSION &Dimension, CShaderProgramm **ProgrammArray )
{
    if ( SDL_Init ( SDL_INIT_VIDEO ) < 0 )
    {
        fprintf ( stderr, "Error while initializing SDL: %s\n", SDL_GetError() );
        return false;;
    }

    USHORT ComponentDepth = PIXEL_COLOR_DEPTH / 4;

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1 );
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,   ComponentDepth);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, ComponentDepth);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,  ComponentDepth);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, ComponentDepth);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  PIXEL_COLOR_DEPTH / 2 );
    SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 0 );//16

    SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE,  0);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE,    0);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 0);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE,    0);

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (Dimension.multisampleBufferCount > 0) );
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,  Dimension.multisampleBufferCount );

    if ( SDL_SetVideoMode ( Dimension.width, Dimension.height, PIXEL_COLOR_DEPTH, Dimension.sdlFlags  ) == NULL ) // | SDL_FULLSCREEN | SDL_HWSURFACE | SDL_OPENGL
    {
        printf ( "Error while setting up video mode: %s\n", SDL_GetError() );
        return false;;
    }

    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); //Automatic repesd pushed key

    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
    glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

    glEnable ( GL_DEPTH_TEST );
    programmArray = ProgrammArray;

#ifdef WIN32
	GLenum err = glewInit();
    if (GLEW_OK != err)
	{
	   /* Problem: glewInit failed, something is seriously wrong. */
    	printf ( "Error in glewInit\n" );
	}
#endif

    dimension = Dimension;
    InitializeFrameBuffer( Dimension.width, Dimension.height );
    initialized = true;

    return true;
}

void
CRenderer::SetRenderParams( const VISUAL_PARAMS &Parametrs )
{

}

bool
CRenderer::StartNewFrame( GLuint VertexArrayName )
{
	bool Result = false;

	if ( initialized )
	{
		glViewport( 0, 0, dimension.width, dimension.height );
		glClearColor( 0.3f, 0.3f, 0.0f, 1.0f );
		glClearDepth( 1.0f );
		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

		glBindVertexArray( VertexArrayName );
		Result = true;
	}
	return Result;
}

void
CRenderer::RenderCameraFrame( const std::vector<GAME_OBJECT*> &SortedVisibleObjects, const GAME_CAMERA &CurrentCamera )
{
	frameBuffer->BindFrameBuffer( true );

	glViewport( 0, 0, dimension.width, dimension.height );
	glClearColor( 1.0f, 0.3f, 0.5f, 1.0f );
	glClearDepth( 1.0f );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	for ( std::vector<GAME_OBJECT*>::const_iterator it = SortedVisibleObjects.begin(), end = SortedVisibleObjects.end(); it != end; ++it )
	{
		GAME_OBJECT *TmpGameObject = *it.base();

		RenderObject( TmpGameObject, CurrentCamera.projectionMatrix, CurrentCamera.viewMatrix, CurrentCamera.lightDirection,
                      CurrentCamera.amblientColor );
	}
	frameBuffer->BindFrameBuffer( false );
	textureBuffer->UpdateBuffer();

#ifdef DEBUG_RENDERER
	if ( !frameBuffer->CheckFrameBuffer() )
	{
		fprintf( stdout, "Broken frame buffer\n" );
		exit(0);
	}
#endif
}

GLuint TmpTextName = 0;

void
CRenderer::RenderFinalFrame( const std::vector<GUI_ELEMENT*> &GuiElementsArray, const RENDER_MODEL_INFO* Dummy ) //TODO Передать в качестве параметра чучело для вывода объектов
{
	CShaderProgramm *CurrentShader = programmArray[SHADER_TYPE_GUI];
	CurrentShader->ActivateShader( true ); // Start programm

	//Render texture buffer content
	GLuint TextureBuffer = textureBuffer->GetBufferName();//Texture;//
	RenderGuiObject( TextureBuffer, glm::vec2( 0.0f, 0.0f ), glm::vec2( 0.5f, 0.5f ), Dummy, CurrentShader );

	for ( std::vector<GUI_ELEMENT*>::const_iterator it = GuiElementsArray.begin(), end = GuiElementsArray.end(); it != end; ++it )
		{
			GUI_ELEMENT *TmpElement = *it.base();
			RenderGuiObject( TmpElement->texture, TmpElement->position, TmpElement->sizes, Dummy, CurrentShader );
		}

	CurrentShader->ActivateShader( false );

	glFlush();
	SDL_GL_SwapBuffers();
	//glBindVertexArray(0); //TODO Может и не нужно это делать
}

void
CRenderer::RenderObject( GAME_OBJECT* Object, const glm::mat4 ProjectionMatrix, const glm::mat4 ViewMatrix,
		                 const glm::vec3 LightDirection, const glm::vec3 AmblientColor )
{
	for ( std::vector<GAME_SUBOBJECT_INSTANCE*>::iterator it = Object->models.begin(), end = Object->models.end(); it != end; ++it )
	{
		GAME_SUBOBJECT_INSTANCE* Subobject = *it.base();
		RENDER_MODEL_INFO*       RenderInfo = Subobject->renderInfo;

		glm::mat4 ModelViewProjection =  ProjectionMatrix * ViewMatrix * Object->worldMatrix * Subobject->position; //TODO Добавить обработку скелетной анимации
		/*fprintf( stderr, "[ModelViewProjection] %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f\n",
        		ModelViewProjection[0].x , ModelViewProjection[0].y, ModelViewProjection[0].z, ModelViewProjection[0].w,
        		ModelViewProjection[1].x , ModelViewProjection[1].y, ModelViewProjection[1].z, ModelViewProjection[1].w,
        		ModelViewProjection[2].x , ModelViewProjection[2].y, ModelViewProjection[2].z, ModelViewProjection[2].w,
        		ModelViewProjection[3].x , ModelViewProjection[3].y, ModelViewProjection[3].z, ModelViewProjection[3].w);*/

		CShaderProgramm *CurrentShader = programmArray[RenderInfo->shaderType];//RenderInfo->shaderType
		CurrentShader->ActivateShader( true ); // Start programm

		//Set standart params
		CurrentShader->SetParam( SHADER_INPUT_MVP_MATRIX, ModelViewProjection );
		CurrentShader->SetParam( SHADER_INPUT_MODEL_MATRIX, Subobject->position );
		CurrentShader->SetParam( SHADER_INPUT_LIGHT_DIRECTION, LightDirection );
		CurrentShader->SetParam( SHADER_INPUT_AMBLIENT_COLOR, AmblientColor );
		//CurrentShader->SetParam( SHADER_INPUT_CAMERA_DISTANCE, Object->distanceToCamera); //FIXME Не используется в шейдере

		for ( UINT i = 0; i < Subobject->textures.size(); i++ )
		{
			glActiveTexture( GL_TEXTURE0 + i );
			glBindTexture( GL_TEXTURE_2D, Subobject->textures[i] );
			TmpTextName = Subobject->textures[i];
		}

		glDrawRangeElementsBaseVertex( RenderInfo->primitiveType, 0, RenderInfo->vertexCount, RenderInfo->indexes.size(),
				             	 	   RenderInfo->indexType, RenderInfo->indexes.data(), RenderInfo->offsetInVertexBuffer );
		CurrentShader->ActivateShader( false );
	}
}

void
CRenderer::RenderGuiObject( const GLuint Texture, const glm::vec2 &Position, const glm::vec2 &Size, const RENDER_MODEL_INFO* Dummy,
		                    CShaderProgramm *CurrentShader )
{
	glm::mat4 GuiMatrix = CalculateGuiMatrix( Position, Size );

	/*fprintf( stderr, "[GuiMatrix] %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f\n",
			GuiMatrix[0].x , GuiMatrix[0].y, GuiMatrix[0].z, GuiMatrix[0].w,
			GuiMatrix[1].x , GuiMatrix[1].y, GuiMatrix[1].z, GuiMatrix[1].w,
			GuiMatrix[2].x , GuiMatrix[2].y, GuiMatrix[2].z, GuiMatrix[2].w,
			GuiMatrix[3].x , GuiMatrix[3].y, GuiMatrix[3].z, GuiMatrix[3].w);*/

	//Setup full screen matrix
	CurrentShader->SetParam( SHADER_INPUT_MVP_MATRIX, GuiMatrix );

	glEnable(GL_TEXTURE_2D);

	glActiveTexture( GL_TEXTURE0 );
	glBindTexture( GL_TEXTURE_2D, Texture );
	glDrawRangeElementsBaseVertex( Dummy->primitiveType, 0, Dummy->vertexCount, Dummy->indexes.size(),
						 	 	   Dummy->indexType, Dummy->indexes.data(), Dummy->offsetInVertexBuffer );

	/*fprintf( stderr, "[RenderGuiObject] Vertex: %u, indexes: %u, offset: %u\n[INDEX]",
			 Dummy->vertexCount, Dummy->indexes.size(), Dummy->offsetInVertexBuffer );
	for ( int i = 0; i < Dummy->indexes.size(); i++ )
	{
		fprintf( stderr, " %u,", Dummy->indexes[i] );
	}

	fprintf( stderr, "\n" );
*/

/*glColor3f(0.0, 1.0, 1.0);
		glBegin(GL_QUADS);
			glTexCoord2f(0, 1);
			glVertex2f( Position.x, Position.y );
			glTexCoord2f(1, 1);
			glVertex2f( Size.x + Position.x, Position.y );
			glTexCoord2f(1, 0);
			glVertex2f(Size.x + Position.x, Size.y + Position.y);
			glTexCoord2f(0, 0);
			glVertex2f( Position.x,  Size.y + Position.y );
		glEnd();*/
}

void
CRenderer::InitializeFrameBuffer( const UINT WindowWidth, const UINT WindowHeight )
{
	frameBuffer = new CFrameBuffer();

    textureBuffer = new CTextureRenderBuffer();
    textureBuffer->TuneRenderBuffer( GL_RGBA, WindowWidth, WindowHeight );

    frameBuffer->BindFrameBuffer(true);

    renderBuffer = new CColorRenderBuffer(); //TODO перенести это до BindFrameBuffer()
    renderBuffer->TuneRenderBuffer( GL_DEPTH_COMPONENT, WindowWidth, WindowHeight );

    frameBuffer->SetRenderTarget( GL_COLOR_ATTACHMENT0, textureBuffer );
    frameBuffer->SetRenderTarget( GL_DEPTH_ATTACHMENT, renderBuffer );

    frameBuffer->BindFrameBuffer(false);
}

glm::mat4
CRenderer::CalculateGuiMatrix( const glm::vec2 &Position, const glm::vec2 &Sizes ) const
{
	glm::vec2 Translation;
	Translation.x = Position.x - 0.5;
	Translation.y = Position.y - 0.5;

	glm::vec2 Factors;
	Factors.x = Sizes.x * 2.0;
	Factors.y = Sizes.y * 2.0;

	glm::mat4 ScaleMatrix = glm::scale( glm::mat4(1.0f), glm::vec3( Factors, 1.0 ) );
	glm::mat4 TransitionMatrix/*(1.0);*/ = glm::translate( ScaleMatrix, glm::vec3( Translation, 0.0 ) );


	return TransitionMatrix;
}
